/**
 * the following commands are shell related
 */

#include "terminal.h"
#include "command.h"
#include "mstring.h"
#include "array.h"
#include "timer.h"
#include "ansi.h"

static mx_size_t cmd_echo_entry (mx_usize_t argc, char** argv)
{
    mx_string_pt fmt = "%s ";
    mx_usize_t lastidx = argc - 1;
    for (mx_usize_t i = 1; i < argc; i++)
    {
        if (i == lastidx)
            fmt = "%s" ENDL;
        mx_terminal_write(fmt, argv[i]);
    }
    return MX_TERMINAL_EXECRET_OK;
}
MX_TERMINAL_EXPORT("echo", cmd_echo_entry);

static mx_size_t cmd_test_entry (mx_usize_t argc, char** argv)
{
    mx_command_help("Usage: test <arg1> <expr> <arg2>");
    mx_command_ckerror(argc != 4);

    mx_size_t ret = MX_TERMINAL_EXECRET_ERR;

    /* string compare */
    if (mx_strcmp(argv[2], "=") == 0)
    {
        if (mx_strcmp(argv[1], argv[3]) == 0)
            ret = MX_TERMINAL_EXECRET_OK;
    }
    else if (mx_strcmp(argv[2], "!=") == 0)
    {
        if (mx_strcmp(argv[1], argv[3]) != 0)
            ret = MX_TERMINAL_EXECRET_OK;
    }
    /* number compare */
    else if (mx_strcmp(argv[2], "-eq") == 0)
    {
        if (mx_strisdigit(argv[1]) && mx_strisdigit(argv[3]) &&
            mx_strtoi(argv[1]) == mx_strtoi(argv[3]))
            ret = MX_TERMINAL_EXECRET_OK;
    }
    else if (mx_strcmp(argv[2], "-ne") == 0)
    {
        if (mx_strisdigit(argv[1]) && mx_strisdigit(argv[3]) &&
            mx_strtoi(argv[1]) != mx_strtoi(argv[3]))
            ret = MX_TERMINAL_EXECRET_OK;
    }
    else if (mx_strcmp(argv[2], "-gt") == 0)
    {
        if (mx_strisdigit(argv[1]) && mx_strisdigit(argv[3]) &&
            mx_strtoi(argv[1]) > mx_strtoi(argv[3]))
            ret = MX_TERMINAL_EXECRET_OK;
    }
    else if (mx_strcmp(argv[2], "-lt") == 0)
    {
        if (mx_strisdigit(argv[1]) && mx_strisdigit(argv[3]) &&
            mx_strtoi(argv[1]) < mx_strtoi(argv[3]))
            ret = MX_TERMINAL_EXECRET_OK;
    }
    else if (mx_strcmp(argv[2], "-ge") == 0)
    {
        if (mx_strisdigit(argv[1]) && mx_strisdigit(argv[3]) &&
            mx_strtoi(argv[1]) >= mx_strtoi(argv[3]))
            ret = MX_TERMINAL_EXECRET_OK;
    }
    else if (mx_strcmp(argv[2], "-le") == 0)
    {
        if (mx_strisdigit(argv[1]) && mx_strisdigit(argv[3]) &&
            mx_strtoi(argv[1]) <= mx_strtoi(argv[3]))
            ret = MX_TERMINAL_EXECRET_OK;
    }
    else
    {
        mx_command_perror("unknown expression");
        ret = MX_TERMINAL_EXECRET_ERR_USAGE;
    }
    return ret;
}
MX_TERMINAL_EXPORT("test", cmd_test_entry);

/* compare string from start-index to the str's end-index, and return the end index (ignore spaces). */
static mx_size_t strcmpcs_igspace (mx_string_pt str, mx_string_pt cs)
{
    mx_size_t ret = -1;
    mx_str_foreachi(cs, pc, i, {
        if (*pc != ' ')
        {
            mx_size_t j = 0;
            mx_str_foreachi(str, ipc, ii, {
                j = ii;
                if (*ipc != pc[ii])
                    return -1;
            });
            ret = i + j + 1;
            break;
        }
    });
    return ret;
}

/* if <cmd>;[cmd;...;] then <cmd>;[cmd;...;] [else <cmd>;[cmd;...;]] fi */
static mx_size_t cmd_if_entry (mx_usize_t argc, char** argv)    // FIXME: not support self nesting
{
    mx_command_ckerror(argc < 3);
    /* recover command */
    mx_string_pt cmd = mx_command_argvs2str();
    /* find the `; then` */
    mx_size_t idx = -1;
    mx_size_t idxet;
    mx_str_foreachi(cmd, pc, i, {
        if (*pc == ';')
        {
            idxet = strcmpcs_igspace("then ", pc + 1);
            if (idxet != -1)
            {
                idx = i;
                idxet = i + idxet + 1;  // idxet is index of "then `string"
                break;
            }
        }
    });
    if (idx == -1)
    {
        mx_command_perror("operator expected `; then`");
        return MX_TERMINAL_EXECRET_ERR_USAGE;
    }
    /* get the condstr command */
    cmd[idx] = '\0';
    mx_string_pt condstr = &argv[1][0];
    /* get the condstr=true command */
    mx_string_pt cond1str = &cmd[idxet];

    /* find the `; else` */
    idx = -1;
    mx_str_foreachi(cond1str, pc, i, {
        if (*pc == ';')
        {
            idxet = strcmpcs_igspace("else ", pc + 1);
            if (idxet != -1)
            {
                idx = i;
                idxet = i + idxet + 1;  // idxet is index of "else `string"
                break;
            }
        }
    });
    mx_string_pt cond0str = mx_null;
    if (idx != -1)
    {
        cond1str[idx] = '\0'; // terminate the condstr=true command
        cond0str = &cond1str[idxet];
    }

    /* find the `; fi` */
    mx_string_pt start = cond0str == mx_null ? cond1str : cond0str;
    idx = -1;
    mx_str_foreachi(start, pc, i, {
        if (*pc == ';')
        {
            idxet = strcmpcs_igspace("fi", pc + 1);
            if (idxet != -1)
            {
                idx = i;
                idxet = i + idxet + 1;  // idxet is index of "fi`\0"
                break;
            }
        }
    });
    if (idx != -1 && start[idxet] == '\0')
    {
        start[idx] = '\0';  // terminate command
    }
    else
    {
        mx_command_perror("operator expected `; fi`");
        return MX_TERMINAL_EXECRET_ERR_USAGE;
    }

    /* support ! */
    mx_bool_t isnot;
    if (condstr[0] == '!')
    {
        condstr ++;
        isnot = mx_true;
    }
    else
        isnot = mx_false;

    mx_bool_t cond;
    /* execute condition command */
    switch (mx_terminal_run(condstr))
    {
    case MX_TERMINAL_EXECRET_OK:
        cond = mx_true; break;
    case MX_TERMINAL_EXECRET_BREAK:
        return MX_TERMINAL_EXECRET_BREAK;       // maybe return here
    case MX_TERMINAL_EXECRET_ERR_SYNTAX:
        return MX_TERMINAL_EXECRET_ERR_SYNTAX;  // maybe return here
    default:
        cond = mx_false; break;
    }
    /* execute target command */
    mx_size_t ret = MX_TERMINAL_EXECRET_OK;
    if (isnot ? !cond : cond)
        ret = mx_terminal_run(cond1str);
    else
    {
        if (cond0str != mx_null)
            ret = mx_terminal_run(cond0str);
    }
    return ret;
}
MX_TERMINAL_EXPORT("if", "fi", cmd_if_entry);

static mx_size_t cmd_sleep_entry (mx_usize_t argc, char** argv)
{
    mx_command_ckerror(argc != 2);

    mx_size_t ret = MX_TERMINAL_EXECRET_OK;
    mx_int32_t s;
    if (mx_strisdigit(argv[1]) && (s = mx_strtoi(argv[1])) != 0)
    {
        if (mx_terminal_read(MX_TIMER_S(s)) != -1)
            ret = MX_TERMINAL_EXECRET_BREAK;
    }
    else
    {
        mx_command_perror("illegal parameters");
        ret = MX_TERMINAL_EXECRET_ERR_USAGE;
    }
    return ret;
}
MX_TERMINAL_EXPORT("sleep", cmd_sleep_entry);

static mx_size_t cmd_expr_entry (mx_usize_t argc, char** argv)
{
    mx_command_ckerror(argc != 4);

    mx_size_t ret = MX_TERMINAL_EXECRET_OK;
    if (mx_strisdigit(argv[1]) && mx_strisdigit(argv[3]))
    {
        mx_int32_t l = mx_strtoi(argv[1]);
        mx_int32_t r = mx_strtoi(argv[3]);
        switch (*argv[2])
        {
        case '+':
            mx_terminal_writel("%d", l + r);
            break;
        case '-':
            mx_terminal_writel("%d", l - r);
            break;
        case '*':
            mx_terminal_writel("%d", l * r);
            break;
        case '/':
            if (r != 0)
                mx_terminal_writel("%d", l / r);
            else
            {
                ret = MX_TERMINAL_EXECRET_ERR;
                mx_command_perror("division by zero");
            }
            break;
        case '%':
            if (r != 0)
                mx_terminal_writel("%d", l % r);
            else
            {
                ret = MX_TERMINAL_EXECRET_ERR;  
                mx_command_perror("division by zero");
            }
            break;
        default:
            ret = MX_TERMINAL_EXECRET_ERR_USAGE;
            mx_command_perror("operators can only contain +-*/%");
            break;
        }
    }
    else
    {
        ret = MX_TERMINAL_EXECRET_ERR_USAGE;
        mx_command_perror("operand must be a integer");
    }
    return ret;
}
MX_TERMINAL_EXPORT("expr", cmd_expr_entry);

static mx_size_t cmd_true_entry (mx_usize_t argc, char** argv)
{
    return MX_TERMINAL_EXECRET_OK;
}
MX_TERMINAL_EXPORT("true", cmd_true_entry);

static mx_size_t cmd_false_entry (mx_usize_t argc, char** argv)
{
    return MX_TERMINAL_EXECRET_ERR;
}
MX_TERMINAL_EXPORT("false", cmd_false_entry);

static mx_size_t cmd_break_entry (mx_usize_t argc, char** argv)
{
    return MX_TERMINAL_EXECRET_BREAK;
}
MX_TERMINAL_EXPORT("break", cmd_break_entry);

/* while <cmd>;[cmd;...;] do <cmd>;[cmd;...;]  done */
static mx_size_t cmd_while_entry (mx_usize_t argc, char** argv) // FIXME: not support self nesting
{
    mx_command_ckerror(argc < 3);
    /* recover command */
    mx_string_pt cmd = mx_command_argvs2str();
    /* find the `; do` */
    mx_size_t idx = -1;
    mx_size_t idxet;
    mx_str_foreachi(cmd, pc, i, {
        if (*pc == ';')
        {
            idxet = strcmpcs_igspace("do ", pc + 1);
            if (idxet != -1)
            {
                idx = i;
                idxet = i + idxet + 1;  // idxet is index of "do `string"
                break;
            }
        }
    });
    if (idx == -1)
    {
        mx_command_perror("operator expected `; do`");
        return MX_TERMINAL_EXECRET_ERR_USAGE;
    }
    /* get the condstr command */
    cmd[idx] = '\0';
    mx_string_pt condstr = &argv[1][0];
    /* get the dostr command */
    mx_string_pt dostr = &cmd[idxet];

    /* find the `; done` */
    idx = -1;
    mx_str_foreachi(dostr, pc, i, {
        if (*pc == ';')
        {
            idxet = strcmpcs_igspace("done", pc + 1);
            if (idxet != -1)
            {
                idx = i;
                idxet = i + idxet + 1;  // idxet is index of "done`\0"
                break;
            }
        }
    });
    if (idx != -1 && dostr[idxet] == '\0')
    {
        dostr[idx] = '\0';  // terminate command
    }
    else
    {
        mx_command_perror("operator expected `; done`");
        return MX_TERMINAL_EXECRET_ERR_USAGE;
    }

    /* support ! */
    mx_bool_t isnot;
    if (condstr[0] == '!')
    {
        condstr++;
        isnot = mx_true;
    }
    else
        isnot = mx_false;

    mx_bool_t cond;
    mx_size_t ret = MX_TERMINAL_EXECRET_OK;
    while (mx_true)
    {
        /* execute condition command */
        switch (mx_terminal_runnm(condstr))
        {
        case MX_TERMINAL_EXECRET_OK:
            cond = mx_true; break;
        case MX_TERMINAL_EXECRET_BREAK:
            return MX_TERMINAL_EXECRET_BREAK;       // maybe return here
        case MX_TERMINAL_EXECRET_ERR_SYNTAX:
            return MX_TERMINAL_EXECRET_ERR_SYNTAX;  // maybe return here
        default:
            cond = mx_false; break;
        }
        /* execute do command */
        if (isnot ? !cond : cond)
        {
            ret = mx_terminal_runnm(dostr);
        }
        else
            break;
        /* loop interrupt control, ^C */
        if (ret == MX_TERMINAL_EXECRET_BREAK || (char)mx_terminal_read(MX_TIMER_MS(1)) == _ANSI_C0_ETX)
        {
            ret = MX_TERMINAL_EXECRET_BREAK;
            break;
        }
    }
    return ret;
}
MX_TERMINAL_EXPORT("while", "done", cmd_while_entry);
